#pragma once

#include <QVector>

#include "../math/vertex.h"
#include "../math/Vector3.h"

#include "datainterface.h"
#include "vertexinfo.h"
#include "triangleinfo.h"

namespace PBVP {
	/// 
	/** 
	 *
	 */
	class GeometryData : public DataInterface {
	public:
		/// Default Constructor
		GeometryData();

		/// Destructor
		virtual ~GeometryData();

		/// 
		virtual DataSetType getDataSetType() const {
			return GEOMETRY_DATA;
		}
		/// Get the number of vertices 
		int numOfVertices() const {
			return m_NumVertices;
		}
		/// Get the number of point primitives
		int numOfPoints() const {
			return m_PointIndexArray.size();
		}
		/// Get the number of line segments
		int numOfLines() const {
			return m_LineIndexArray.size()/2;
		}
		/// Get the number of triangles
		int numOfTriangles() const {
			return m_TriangleIndexArray.size()/3;
		}
		/// Get the number of quads
		int numOfQuads() const {
			return m_QuadIndexArray.size()/4;
		}

		bool hasNormalArray() const {
			return (m_VertexArray.size() == m_NormalArray.size());
		}

		bool hasColorArray() const {
			return (m_ColorArray.size() == 3 * numOfVertices());
		}

		bool hasTex2DArray() const {
			return (m_TexCoordArray.size() == 2 * numOfVertices());
		}
		///  Add a vertex 
		/**
		 * @return The id of the newly added vertex
		 */
		int addVertex(float x, float y, float z);
		int addVertex(const Vector3& vertex);
		/// Add vertex and normal
		int addVertex(const Vector3& vertex, const Vector3& normal);
		
		/// Add vertex, normal and color
		int addVertex(const Vector3& vertex, const Vector3& normal, const Vector3& color);

		/// Add an normal vector
		int addNormal(const Vector3& norm);

		/// Add an color vector
		int addColor(const Vector3& color);

		/// <summary>
		/// Add a triangle with given vertex indices
		/// </summary>
		/// <param name="id"></param>
		/// <returns>int: The id of the new triangle</returns>
		int addTriangle(int index[3]);
		int addTriangle(int vx, int vy, int vz);
		
		/// <summary>
		/// Compute vertex normals of the mesh
		/// </summary>
		/// <param name="forced"> forced: If forced is true, forced to recompute normals
		/// Otherwise compute normals only if normals are not available</param>
		virtual void calcNormals(bool forced = false);
		
		/// <summary>
		/// Invert vertex normals 
		/// </summary>
		void invertNormals(); 
		
		/// Invert the orientations of triangular faces
		void invertFaceOrients();

		///
		float* vertexPointer() {
			return m_VertexArray.data();
		}
		
		///
		float* normalPointer() {
			return m_NormalArray.data();
		}
		
		///
		float* colorPointer() {
			return m_ColorArray.data();
		}

		///
		unsigned int* triangleIndexPointer() {
			return m_TriangleIndexArray.data();
		}

		///
		Vector3 getVertex(unsigned int nv) const {
			Vector3 v;
			v.x = m_VertexArray[3*nv];
			v.y = m_VertexArray[3*nv+1];
			v.z = m_VertexArray[3*nv+2];
			return v;
		}
	
		/// 
		Vector3 getNormalVector(unsigned int nv) {
			Vector3 n;
			if (hasNormalArray())
			{
				n.x = m_NormalArray[3*nv];
				n.y = m_NormalArray[3*nv+1];
				n.z = m_NormalArray[3*nv+2];
			}
			return n;
		}

		/// 
		Vector3 getColorVector(unsigned int nv) {
			Vector3 c;
			if (hasColorArray())
			{
				c.x = m_ColorArray[3*nv];
				c.y = m_ColorArray[3*nv+1];
				c.z = m_ColorArray[3*nv+2];
			}
			return c;
		}

		/// 
		void getTriangleIndex(unsigned int nt, unsigned int idx[3])
		{
			idx[0] = m_TriangleIndexArray[3*nt];
			idx[1] = m_TriangleIndexArray[3*nt+1];
			idx[2] = m_TriangleIndexArray[3*nt+2];
		}

		/// 
		void getTriangleVerts(unsigned int nt, Vector3& v0, Vector3& v1, Vector3& v2)
		{
			for (int i = 0; i < 3; i++)
			{
				v0[i] = m_VertexArray[3*m_TriangleIndexArray[3*nt]+i];
				v1[i] = m_VertexArray[3*m_TriangleIndexArray[3*nt+1]+i];
				v2[i] = m_VertexArray[3*m_TriangleIndexArray[3*nt+2]+i];
			}
		}

		/// 
		void setNormalArray(int nverts, float *data)
		{
			assert(nverts <= numOfVertices());
			m_NormalArray.resize(3*numOfVertices());
			for (int i = 0; i < 3*nverts; i++)		// vert normal has 3 components
			{
				m_NormalArray[i] = data[i];
			}
		}

		/// merge additional geometry data into the current one
		void merge(GeometryData *pGeoData);

		/// Put all triangles into consistant orientation
		/** 
		 * @return The number of connected components of the geometry
		 */
		int orientTriangles();

		/// Get the lower and upper corners of the geometry
		void getExtent(Vector3& minExt, Vector3& maxExt) const;

	protected:
		/// Vertex Array
		QVector<float> m_VertexArray;
		/// Normal Array
		QVector<float> m_NormalArray;
		/// Color Array, 3 color components (R, G, B)
		QVector<float> m_ColorArray;
		/// 2D Texture Coordinate Array
		QVector<float> m_TexCoordArray;
		/// Number of vertices in the geometry
		int m_NumVertices;

		/// Points
		QVector<unsigned int> m_PointIndexArray;
		/// Line Segments
		QVector<unsigned int> m_LineIndexArray;
		/// Triangles
		QVector<unsigned int> m_TriangleIndexArray;
		/// Quads
		QVector<unsigned int> m_QuadIndexArray;

	private:
		/**
		 * Get triangle n 's neighbor triangle facing vertex k
		 * @param k = 0, 1 or 2;
		 * @return -1 if there is no neighor
		 */
		int getNeighborTriangle(int n, int k);

		/// Calculate additional connectivity info of vertices and triangles
		void calcGeoInfo();

		/// Clear additional info to release memory
		void clearGeoInfo();

		/// Check if all neighbors of triangle n are already oriented
		bool neighborsOriented(bool oriented[], int n);

		///
		VertexInfo *m_pVertInfo;
		///
		TriangleInfo *m_pTriInfo;
	};
}